using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
using Matrix.Notification.Domain.Notifications;
using Matrix.Notification.Domain.Shared.Notifications;
using Matrix.Notification.Notifications.Dto;

namespace Matrix.Notification.EntityFrameworkCore.Notifications
{
    /// <summary>
    /// 通知仓储实现
    /// </summary>
    public class NotificationRepository : EfCoreRepository<NotificationDbContext, Notification, Guid>, INotificationRepository
    {
        public NotificationRepository(IDbContextProvider<NotificationDbContext> dbContextProvider)
            : base(dbContextProvider)
        {
        }

        public async Task<Notification?> GetWithDetailsAsync(
            Guid id,
            CancellationToken cancellationToken = default)
        {
            return await DbSet
                .IncludeDetails()
                .FirstOrDefaultAsync(x => x.Id == id, GetCancellationToken(cancellationToken));
        }

        public async Task<List<Notification>> GetListAsync(
            string? sorting = null,
            int maxResultCount = int.MaxValue,
            int skipCount = 0,
            Guid? userId = null,
            NotificationType? type = null,
            NotificationStatus? status = null,
            NotificationChannel? channel = null,
            DateTime? startTime = null,
            DateTime? endTime = null,
            string? category = null,
            CancellationToken cancellationToken = default)
        {
            return await (await GetQueryableAsync())
                .IncludeDetails()
                .WhereIf(userId.HasValue, x => x.RecipientId == userId)
                .WhereIf(type.HasValue, x => x.Type == type.Value)
                .WhereIf(status.HasValue, x => x.Status == status.Value)
                .WhereIf(channel.HasValue, x => x.Channel == channel.Value)
                .WhereIf(startTime.HasValue, x => x.CreationTime >= startTime.Value)
                .WhereIf(endTime.HasValue, x => x.CreationTime <= endTime.Value)
                .WhereIf(!category.IsNullOrWhiteSpace(), x => x.Category == category)
                .OrderBy(sorting ?? nameof(Notification.CreationTime) + " DESC")
                .PageBy(skipCount, maxResultCount)
                .ToListAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<long> GetCountAsync(
            Guid? userId = null,
            NotificationType? type = null,
            NotificationStatus? status = null,
            NotificationChannel? channel = null,
            DateTime? startTime = null,
            DateTime? endTime = null,
            string? category = null,
            CancellationToken cancellationToken = default)
        {
            return await (await GetQueryableAsync())
                .WhereIf(userId.HasValue, x => x.RecipientId == userId)
                .WhereIf(type.HasValue, x => x.Type == type.Value)
                .WhereIf(status.HasValue, x => x.Status == status.Value)
                .WhereIf(channel.HasValue, x => x.Channel == channel.Value)
                .WhereIf(startTime.HasValue, x => x.CreationTime >= startTime.Value)
                .WhereIf(endTime.HasValue, x => x.CreationTime <= endTime.Value)
                .WhereIf(!category.IsNullOrWhiteSpace(), x => x.Category == category)
                .LongCountAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<List<Notification>> GetPendingNotificationsAsync(
            int maxCount = 100,
            CancellationToken cancellationToken = default)
        {
            return await (await GetQueryableAsync())
                .IncludeDetails()
                .Where(x => x.Status == NotificationStatus.Pending)
                .WhereIf(x.ScheduledTime.HasValue, x => x.ScheduledTime <= DateTime.UtcNow)
                .OrderBy(x => x.Priority)
                .ThenBy(x => x.CreationTime)
                .Take(maxCount)
                .ToListAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<List<Notification>> GetRetryableNotificationsAsync(
            int maxCount = 50,
            CancellationToken cancellationToken = default)
        {
            return await (await GetQueryableAsync())
                .IncludeDetails()
                .Where(x => x.Status == NotificationStatus.Failed && x.CanRetry())
                .Where(x => x.NextRetryTime.HasValue && x.NextRetryTime <= DateTime.UtcNow)
                .OrderBy(x => x.Priority)
                .ThenBy(x => x.NextRetryTime)
                .Take(maxCount)
                .ToListAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<List<Notification>> GetScheduledNotificationsAsync(
            CancellationToken cancellationToken = default)
        {
            return await (await GetQueryableAsync())
                .IncludeDetails()
                .Where(x => x.Status == NotificationStatus.Scheduled)
                .Where(x => x.ScheduledTime.HasValue && x.ScheduledTime <= DateTime.UtcNow)
                .OrderBy(x => x.ScheduledTime)
                .ToListAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<List<Notification>> GetUnreadNotificationsAsync(
            Guid userId,
            int maxCount = 10,
            CancellationToken cancellationToken = default)
        {
            return await (await GetQueryableAsync())
                .IncludeDetails()
                .Where(x => x.RecipientId == userId)
                .Where(x => x.Status == NotificationStatus.Delivered)
                .OrderByDescending(x => x.CreationTime)
                .Take(maxCount)
                .ToListAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<List<Notification>> GetNotificationsByBatchIdAsync(
            Guid batchId,
            CancellationToken cancellationToken = default)
        {
            return await (await GetQueryableAsync())
                .IncludeDetails()
                .Where(x => x.BatchId == batchId)
                .OrderBy(x => x.CreationTime)
                .ToListAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<List<Notification>> GetNotificationsByExternalIdAsync(
            string externalId,
            CancellationToken cancellationToken = default)
        {
            return await (await GetQueryableAsync())
                .IncludeDetails()
                .Where(x => x.ExternalId == externalId)
                .OrderByDescending(x => x.CreationTime)
                .ToListAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<List<Notification>> GetExpiredNotificationsAsync(
            CancellationToken cancellationToken = default)
        {
            return await (await GetQueryableAsync())
                .IncludeDetails()
                .Where(x => !x.IsArchived)
                .Where(x => x.IsExpired())
                .ToListAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<List<Notification>> GetNotificationsForCleanupAsync(
            DateTime beforeDate,
            CancellationToken cancellationToken = default)
        {
            return await (await GetQueryableAsync())
                .IncludeDetails()
                .Where(x => x.IsArchived && x.ArchivedTime.HasValue && x.ArchivedTime < beforeDate)
                .ToListAsync(GetCancellationToken(cancellationToken));
        }

        public async Task<NotificationStatisticsDto> GetStatisticsAsync(
            Guid? userId = null,
            DateTime? startTime = null,
            DateTime? endTime = null,
            CancellationToken cancellationToken = default)
        {
            var queryable = await GetQueryableAsync();

            queryable = queryable
                .WhereIf(userId.HasValue, x => x.RecipientId == userId)
                .WhereIf(startTime.HasValue, x => x.CreationTime >= startTime.Value)
                .WhereIf(endTime.HasValue, x => x.CreationTime <= endTime.Value);

            var statistics = new NotificationStatisticsDto
            {
                TotalCount = await queryable.LongCountAsync(GetCancellationToken(cancellationToken)),
                SentCount = await queryable.CountAsync(x => x.Status == NotificationStatus.Sent, GetCancellationToken(cancellationToken)),
                DeliveredCount = await queryable.CountAsync(x => x.Status == NotificationStatus.Delivered, GetCancellationToken(cancellationToken)),
                ReadCount = await queryable.CountAsync(x => x.Status == NotificationStatus.Read, GetCancellationToken(cancellationToken)),
                FailedCount = await queryable.CountAsync(x => x.Status == NotificationStatus.Failed, GetCancellationToken(cancellationToken)),
                PendingCount = await queryable.CountAsync(x => x.Status == NotificationStatus.Pending, GetCancellationToken(cancellationToken))
            };

            // 按类型统计
            statistics.ByType = await queryable
                .GroupBy(x => x.Type)
                .Select(g => new { Type = g.Key, Count = g.LongCount() })
                .ToListAsync(GetCancellationToken(cancellationToken));

            // 按渠道统计
            statistics.ByChannel = await queryable
                .GroupBy(x => x.Channel)
                .Select(g => new { Channel = g.Key, Count = g.LongCount() })
                .ToListAsync(GetCancellationToken(cancellationToken));

            return statistics;
        }

        public async Task UpdateStatusAsync(
            Guid id,
            NotificationStatus newStatus,
            string? failureReason = null,
            CancellationToken cancellationToken = default)
        {
            var dbContext = await GetDbContextAsync();
            var notification = await DbSet.FindAsync(new object[] { id }, GetCancellationToken(cancellationToken));

            if (notification != null)
            {
                switch (newStatus)
                {
                    case NotificationStatus.Sent:
                        notification.MarkAsSent();
                        break;
                    case NotificationStatus.Delivered:
                        notification.MarkAsDelivered();
                        break;
                    case NotificationStatus.Read:
                        notification.MarkAsRead();
                        break;
                    case NotificationStatus.Failed:
                        notification.MarkAsFailed(failureReason ?? "Unknown error");
                        break;
                }

                await dbContext.SaveChangesAsync(GetCancellationToken(cancellationToken));
            }
        }

        public async Task UpdateMultipleStatusAsync(
            List<Guid> ids,
            NotificationStatus newStatus,
            string? failureReason = null,
            CancellationToken cancellationToken = default)
        {
            var dbContext = await GetDbContextAsync();
            var notifications = await DbSet
                .Where(x => ids.Contains(x.Id))
                .ToListAsync(GetCancellationToken(cancellationToken));

            foreach (var notification in notifications)
            {
                switch (newStatus)
                {
                    case NotificationStatus.Sent:
                        notification.MarkAsSent();
                        break;
                    case NotificationStatus.Delivered:
                        notification.MarkAsDelivered();
                        break;
                    case NotificationStatus.Read:
                        notification.MarkAsRead();
                        break;
                    case NotificationStatus.Failed:
                        notification.MarkAsFailed(failureReason ?? "Batch error");
                        break;
                }
            }

            await dbContext.SaveChangesAsync(GetCancellationToken(cancellationToken));
        }
    }
}